﻿using System;
using System.Linq;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using Windows.ApplicationModel.Resources.Core;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Media.Imaging;
using SelfMeter.Data.Test;
using Windows.Storage;
using System.IO;
using System.Xml.Serialization;
using System.Threading.Tasks;
using Windows.Storage.Search;
using Windows.UI.Xaml;
using Windows.UI.Popups;

// The data model defined by this file serves as a representative example of a strongly-typed
// model that supports notification when members are added, removed, or modified.  The property
// names chosen coincide with data bindings in the standard item templates.
//
// Applications may use this model as a starting point and build on it, or discard it entirely and
// replace it with something appropriate to their needs.

namespace SelfMeter.Data
{
    /// <summary>
    /// Base class for <see cref="SelfMeterDataItem"/> and <see cref="SelfMeterDataGroup"/> that
    /// defines properties common to both.
    /// </summary>
    [Windows.Foundation.Metadata.WebHostHidden]
    public abstract class SelfMeterDataCommon : SelfMeter.Common.BindableBase
    {
        private static Uri _baseUri = new Uri("ms-appx:///");

        public SelfMeterDataCommon(String uniqueId, String title, String subtitle, String imagePath, String description)
        {
            this._uniqueId = uniqueId;
            this._title = title;
            this._subtitle = subtitle;
            this._description = description;
            this._imagePath = imagePath;
        }

        private string _uniqueId = string.Empty;
        public string UniqueId
        {
            get { return this._uniqueId; }
            set { this.SetProperty(ref this._uniqueId, value); }
        }

        private string _title = string.Empty;
        public string Title
        {
            get { return this._title; }
            set { this.SetProperty(ref this._title, value); }
        }

        private string _subtitle = string.Empty;
        public string Subtitle
        {
            get { return this._subtitle; }
            set { this.SetProperty(ref this._subtitle, value); }
        }

        private string _description = string.Empty;
        public string Description
        {
            get { return this._description; }
            set { this.SetProperty(ref this._description, value); }
        }

        private ImageSource _image = null;
        private String _imagePath = null;
        public ImageSource Image
        {
            get
            {
                if (this._image == null && this._imagePath != null)
                {
                    this._image = new BitmapImage(new Uri(SelfMeterDataCommon._baseUri, this._imagePath));
                }
                return this._image;
            }

            set
            {
                this._imagePath = null;
                this.SetProperty(ref this._image, value);
            }
        }

        public void SetImage(String path)
        {
            this._image = null;
            this._imagePath = path;
            this.OnPropertyChanged("Image");
        }
    }

    public interface ISelfMeterView
    {
        void UpdateView(MeterDefinition meter, bool load);
    }
    
    /// <summary>
    /// Generic item data model.
    /// </summary>
    public class SelfMeterDataItem : SelfMeterDataCommon
    {
        public SelfMeterDataItem(String uniqueId, String title, String subtitle, String imagePath, String description, UserControl sample, SelfMeterDataGroup group, ISelfMeterView smView)
            : base(uniqueId, title, subtitle, imagePath, description)
        {
            this._sampleStore = sample;
            this._group = group;
            this._view = smView;
        }

        UserControl _sample, _sampleStore = null;
        public UserControl Sample
        {
          get { return this._sample; }
          set { this.SetProperty(ref this._sample, value); }
        }

        private SelfMeterDataGroup _group;
        public SelfMeterDataGroup Group
        {
            get { return this._group; }
            set { this.SetProperty(ref this._group, value); }
        }

        
        private ISelfMeterView _view;
        public void LoadSample()
        {
          Sample = _sampleStore;
          _view.UpdateView(Group.Meter, true);
        }

        public void UnloadSample()
        {
            _view.UpdateView(Group.Meter, false);
            Sample = null;
        }
    }

    /// <summary>
    /// Generic group data model.
    /// </summary>
    public class SelfMeterDataGroup : SelfMeterDataCommon
    {
        public SelfMeterDataGroup(String uniqueId, String title, String subtitle, String imagePath, String description, Visibility selectable, MeterDefinition meter)
            : base(uniqueId, title, subtitle, imagePath, description)
        {
            Selectable = selectable;
            Meter = meter;
        }

        private Visibility _selectable;
        public Visibility Selectable
        {
            get { return this._selectable;  }
            set { this.SetProperty(ref this._selectable, value); }
        }

        private bool _selected;
        public bool Selected
        {
            get { return this._selected; }
            set { this.SetProperty(ref this._selected, value); }
        }

        private ObservableCollection<SelfMeterDataItem> _items = new ObservableCollection<SelfMeterDataItem>();
        public ObservableCollection<SelfMeterDataItem> Items
        {
            get { return this._items; }
        }

        public MeterDefinition Meter { get; set; }

        public IEnumerable<SelfMeterDataItem> TopItems
        {
            // Provides a subset of the full items collection to bind to from a GroupedItemsPage
            // for two reasons: GridView will not virtualize large items collections, and it
            // improves the user experience when browsing through groups with large numbers of
            // items.
            //
            // A maximum of 12 items are displayed because it results in filled grid columns
            // whether there are 1, 2, 3, 4, or 6 rows displayed
            get { return this._items.Take(12); }
        }
    }

    /// <summary>
    /// Creates a collection of groups and items with hard-coded content.
    /// </summary>
    public sealed class SelfMeterDataSource
    {
        private static string _fileSuffix = ".smx";
        private static SelfMeterDataSource _sampleDataSource = new SelfMeterDataSource();
        
        private ObservableCollection<SelfMeterDataGroup> _allGroups = new ObservableCollection<SelfMeterDataGroup>();
        private StorageFolder _saveFolder = ApplicationData.Current.RoamingFolder;
        private SelfMeterDataGroup _newGroup = null;

        public ObservableCollection<SelfMeterDataGroup> AllGroups
        {
            get { return this._allGroups; }
        }

        public void OrderByName()
        {
            if (AllGroups.Count > 0)
            {
                var array = AllGroups.ToArray();
                Array.Sort(array, (a, b) => { return a.Meter.Name.CompareTo(b.Meter.Name); });

                AllGroups.Clear();
                foreach (var g in array)
                {
                    if (g != _newGroup)
                        AllGroups.Add(g);
                }

                if (_newGroup != null)
                {
                    AllGroups.Insert(0, _newGroup);
                }
            }
        }

        public static IEnumerable<SelfMeterDataGroup> GetGroups(string uniqueId)
        {
            if (!uniqueId.Equals("AllGroups")) throw new ArgumentException("Only 'AllGroups' is supported as a collection of groups");
            
            return _sampleDataSource.AllGroups;
        }

        public static void Sort()
        {
            _sampleDataSource.OrderByName();
        }

        public static SelfMeterDataGroup GetGroup(string uniqueId)
        {
            // Simple linear search is acceptable for small data sets
            var matches = _sampleDataSource.AllGroups.Where((group) => group.UniqueId.Equals(uniqueId));
            if (matches.Count() == 1) return matches.First();
            return null;
        }

        public static SelfMeterDataItem GetItem(string uniqueId)
        {
            // Simple linear search is acceptable for small data sets
            var matches = _sampleDataSource.AllGroups.SelectMany(group => group.Items).Where((item) => item.UniqueId.Equals(uniqueId));
            if (matches.Count() == 1) return matches.First();
            return null;
        }

        
        public static async Task<bool> SaveMeterData(MeterDefinition meter)
        {
            bool addNew = false;
            if (meter.ID == Guid.Empty)
            {
                meter.ID = Guid.NewGuid();
                addNew = true;
            }

            bool result = await _sampleDataSource.SaveMeter(meter, addNew);
            return result;
        }

        public static async Task RemoveGroup(SelfMeterDataGroup group)
        {
            var dialog = new MessageDialog(string.Format("Are you sure to remove the data of {0} permanently?", group.Meter.Name), "Confirmation");
            dialog.Commands.Add(new UICommand("Yes", async (c) =>
            {
                var result = await _sampleDataSource.RemoveMeter(group.Meter);
                if (result)
                    _sampleDataSource.AllGroups.Remove(group);
            }));
            dialog.Commands.Add(new UICommand("No", (c) => { }));
            dialog.DefaultCommandIndex = 1;
            var cmd = await dialog.ShowAsync();
        }

        private async void LoadMeters()
        {
            List<string> fileTypeFilter = new List<string>();
            fileTypeFilter.Add(_fileSuffix);
            var queryOptions = new QueryOptions(CommonFileQuery.OrderByName, fileTypeFilter);
            var query = _saveFolder.CreateFileQueryWithOptions(queryOptions);
            var files = await query.GetFilesAsync();
            if (files != null)
            {
                var xmlser = new XmlSerializer(typeof(MeterDefinition));
                foreach (var file in files)
                {
                    var stream = await file.OpenStreamForReadAsync();
                    using (stream)
                    {
                        try
                        {
                            MeterDefinition meter = (MeterDefinition)xmlser.Deserialize(stream);
                            AddGroup(meter);
                        }
                        catch
                        {

                        }
                    }
                }
            }
            //AddGroup(TestDataGen.GenRandomTestData("Weight", "My weight", "kg", DateTime.Today.AddDays(-365), 100, 63, 55));
            Sort();
            _newGroup = AddGroup(NewMeter(), true);
            
        }

        private MeterDefinition NewMeter()
        {
            return new MeterDefinition { ID = Guid.Empty, Name = "NewData", Unit = "kg", Description = "add description to your data ", MeterData = new List<MeterItem>() };
        }

        private SelfMeterDataGroup AddGroup(MeterDefinition meter)
        {
            return AddGroup(meter, false);
        }

        private SelfMeterDataGroup AddGroup(MeterDefinition meter, bool inBegin)
        {
            var group = new SelfMeterDataGroup("Group-" + meter.ID,
                meter.Name,
                meter.Description,
                "Assets/Visualize.png",
                "You can watch your data by grid or by fancy chart!", (meter.ID != Guid.Empty) ? Visibility.Visible : Visibility.Collapsed, meter);
            var edit = new Editing(meter);
            group.Items.Add(new SelfMeterDataItem(group.UniqueId + "-Item-1",
                      "Edit " + meter.Name,
                      "Add records of your data",
                      "Assets/Edit.png",
                      "You can add/delete data records here, and forget to save it",
                      edit,
                      group, edit));
            var chart = new DataChart(meter);
            group.Items.Add(new SelfMeterDataItem(group.UniqueId + "-Item-2",
                      meter.Name + " Chart",
                      "You can visualize your data by the chart",
                      "Assets/Visualize.png",
                      "You can scale the chart with the slider bar, or even cooler you can let it play by itself!",
                      chart,
                      group, chart));

            if (!inBegin)
                AllGroups.Add(group);
            else
                AllGroups.Insert(0, group);
            return group;
        }

        public async Task<bool> RemoveMeter(MeterDefinition meter)
        {
            if (meter.ID == Guid.Empty)
            {
                throw new ArgumentException("can't delete meter whose id is empty");
            }

            var file = await _saveFolder.GetFileAsync(meter.ID + _fileSuffix);
            if (file != null)
            {
                await file.DeleteAsync(StorageDeleteOption.PermanentDelete);
                return true;
            }

            return false;
        }

        public async Task<bool> SaveMeter(MeterDefinition meter, bool addNew)
        {
            var file = await _saveFolder.CreateFileAsync(meter.ID + _fileSuffix, CreationCollisionOption.OpenIfExists);
            var stream = await file.OpenStreamForWriteAsync();
            bool result = false;

            if (stream != null)
            {
                XmlSerializer xmlSer = new XmlSerializer(typeof(MeterDefinition));
            
                using (stream)
                {
                    try
                    {
                        xmlSer.Serialize(stream, meter);
                        result = true;
                    }
                    catch
                    {
                        result = false;
                    }
                }
            }

            if (addNew)
            {
                AddGroup(meter);
                // add an empty meter to the New group
                _newGroup.Meter = NewMeter();
            }

            return result;
        }

        public SelfMeterDataSource()
        {
            //var meter = TestDataGen.GenRandomTestData("Weight", "My weight", "kg", DateTime.Today.AddDays(-365), 100, 63, 55);
            LoadMeters();
        }
    }
}
